using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.ComponentModel;

namespace OpenComposite.EII.Repository
{
	[ToolboxItem(false)]
	public abstract class DataRowItemAbstract
	{
		#region Constants
		/// <summary>
		/// Prefix for compressed strings
		/// </summary>
		internal const string ZIPPREFIX = "!SCZIP0$";
		/// <summary>
		/// Prefix for encrypted and compressed strings
		/// </summary>
		internal const string ENCZIPPREFIX = "!SCZIP1$";
		#endregion

		#region Variables
		internal volatile DataRow row;

		protected string colnmId;
		protected string colnmNm;
		protected string colnmDesc;
		protected string colnmNText;

		protected int id;
		protected string name;

		protected string sNewItemName;

		protected volatile object lockThis = new object();
		protected static object lockAll = new object();
		#endregion

		#region Methods
		/// <summary>
		/// Reloads this instance.
		/// </summary>
		/// <returns><c>true</c> if successfully reloaded.</returns>
		public abstract bool Reload();

		/// <summary>
		/// Called when item changing.
		/// </summary>
		/// <param name="member">The changing member.</param>
		protected abstract bool OnItemChanging(MemberInfo member, object newValue);
		/// <summary>
		/// Called when item changed.
		/// </summary>
		/// <param name="member">The changed member.</param>
		/// <param name="oldValue">The old value.</param>
		/// <param name="newValue">The new value.</param>
		protected abstract void OnItemChanged(MemberInfo member, object oldValue, object newValue);

		/// <summary>
		/// Gets the value of a column.
		/// </summary>
		/// <param name="columnname">The column name.</param>
		/// <param name="dbnullvalue">The default value.</param>
		/// <returns>The value of the column as <see cref="T:System.Object"/>.</returns>
		protected object getValue(string columnname, object dbnullvalue)
		{
			//if ( columnname != this.colnmId &&
			//      columnname != this.colnmNm && row != null && row.RowState == DataRowState.Detached ) return dbnullvalue;
			if ( columnname != this.colnmId &&
				 ( columnname != this.colnmNm || string.IsNullOrEmpty(this.name) ) &&
				 ( row == null || row.IsNull(this.colnmId) || row.IsNull(this.colnmNm) ) ) {
				if ( !Reload() ) return dbnullvalue;
			}
#if DEBUG
			if ( row == null ) return dbnullvalue;
			if ( !row.Table.Columns.Contains(columnname) ) {
				throw new Exception(string.Format("Column '{0}' doesn't exists.", columnname));
			}
#else
			if ( row == null || !row.Table.Columns.Contains(columnname) ) {
				return dbnullvalue;
			}
#endif
			return ( this.row.IsNull(columnname) ? dbnullvalue : this.row[columnname] );
		}
		/// <summary>
		/// Gets the bytes value of a column.
		/// </summary>
		/// <param name="columnname">The column name.</param>
		/// <returns>The value of the column as an array of <see cref="T:System.Byte"/>.</returns>
		protected byte[] getBytes(string columnname)
		{
			return getValue(columnname, null) as byte[];
		}
		/// <summary>
		/// Gets the string value of a column.
		/// </summary>
		/// <param name="columnname">The column name.</param>
		/// <param name="dbnullvalue">The default value.</param>
		/// <returns>The value of the column as a <see cref="T:System.String"/>.</returns>
		protected string getString(string columnname, string dbnullvalue)
		{
			string str = (string)getValue(columnname, dbnullvalue);
			if ( str != null ) {
				if ( str.StartsWith(ZIPPREFIX) ) {
					str = Helpers.DecompressString(str.Remove(0, ZIPPREFIX.Length));
				} else if ( str.StartsWith(ENCZIPPREFIX) ) {
					str = Helpers.DecryptDecompressString(str.Remove(0, ENCZIPPREFIX.Length));
				}
			}
			return str;
		}
		/// <summary>
		/// Gets the int value of a column.
		/// </summary>
		/// <param name="columnname">The column name.</param>
		/// <param name="dbnullvalue">The default value.</param>
		/// <returns>The value of the column as <see cref="T:System.Int32"/>.</returns>
		protected int getInt(string columnname, int dbnullvalue)
		{
			return (int)getValue(columnname, dbnullvalue);
		}
		/// <summary>
		/// Gets the bool value of a column.
		/// </summary>
		/// <param name="columnname">The column name.</param>
		/// <param name="dbnullvalue">if default value.</param>
		/// <returns>The value of the column as <see cref="T:System.Boolean"/>.</returns>
		protected bool getBool(string columnname, bool dbnullvalue)
		{
			return (bool)getValue(columnname, dbnullvalue);
		}
		/// <summary>
		/// Gets the date time value of a column.
		/// </summary>
		/// <param name="columnname">The column name.</param>
		/// <param name="dbnullvalue">The default value.</param>
		/// <returns>The value of the column as <see cref="T:System.DateTime"/>.</returns>
		protected DateTime getDateTime(string columnname, DateTime dbnullvalue)
		{
			return (DateTime)getValue(columnname, dbnullvalue);
		}
		/// <summary>
		/// Gets the xml schema value of a column.
		/// </summary>
		/// <param name="columnname">The column name.</param>
		/// <returns>The value of the column as <see cref="T:System.Xml.Schema.XmlSchema"/>.</returns>
		protected XmlSchema getSchema(string columnname)
		{
			XmlSchema schema = null;
			string xsd = getString(columnname, "");
			if ( xsd == null || xsd == "" ) return schema;
			try {
				using ( StringReader sr = new StringReader(xsd) ) {
					schema = XmlSchema.Read(sr,
						delegate(object sender, ValidationEventArgs e)
						{
							MessageBox.Show(e.Message, e.Exception.Source, MessageBoxButtons.OK, MessageBoxIcon.Error);
						});
				}
			} catch {
				schema = null;
			}
			return schema;
		}
		/// <summary>
		/// Gets the XML document value of a column.
		/// </summary>
		/// <param name="columnname">The column name.</param>
		/// <returns>The value of the column as <see cref="T:System.Xml.XmlDocument"/>.</returns>
		protected XmlDocument getXmlDocument(string columnname)
		{
			XmlDocument xd = null;
			string xml = getString(columnname, "");
			if ( xml == null || xml == "" ) return xd;
			xd = new XmlDocument();
			xd.LoadXml(xml);
			return xd;
		}
		/// <summary>
		/// Gets the XML serialized object value of a column.
		/// </summary>
		/// <typeparam name="T">The type of the serialized object.</typeparam>
		/// <param name="columnname">The column name.</param>
		/// <returns>The value of the column as a deserialized object.</returns>
		protected T getXmlSerializedObject<T>(string columnname) where T : class
		{
			return getXmlSerializedObject<T>(columnname, new Type[] { });
		}
		/// <summary>
		/// Gets the XML serialized object value of a column.
		/// </summary>
		/// <typeparam name="T">The type of the serialized object.</typeparam>
		/// <param name="columnname">The column name.</param>
		/// <param name="additionalTypes">The additional types.</param>
		/// <returns>The value of the column as deserialized object.</returns>
		protected T getXmlSerializedObject<T>(string columnname, Type[] additionalTypes) where T : class
		{
			try {
				string xmlstr = getString(columnname, null);
				if ( xmlstr == null || xmlstr == "" ) return null;
				XmlSerializer xs = null;
				if ( additionalTypes.Length == 0 ) xs = new XmlSerializer(typeof(T));
				else xs = new XmlSerializer(typeof(T), additionalTypes);
				using ( StringReader sr = new StringReader(xmlstr) ) {
					return xs.Deserialize(sr) as T;
				}
			} catch {
				return null;
			}
		}

		/// <summary>
		/// Gets the serialized object value of a column.
		/// </summary>
		/// <typeparam name="T">The type of the serialized object.</typeparam>
		/// <param name="columnname">The column name.</param>
		/// <returns>The value of the column as deserialized object.</returns>
		protected T getSerializedObject<T>(string columnname) where T : class
		{
			try {
				string str = getString(columnname, null);
				if ( str == null || str == "" ) return null;

				IFormatter f = new BinaryFormatter();
				using ( MemoryStream ms = new MemoryStream() ) {
					using ( StreamWriter sw = new StreamWriter(ms) ) {
						sw.Write(str);
					}
					ms.Position = 0;
					return f.Deserialize(ms) as T;
				}
			} catch {
				return null;
			}
		}
		/// <summary>
		/// Gets the GUID value of a column.
		/// </summary>
		/// <param name="columnname">The column name.</param>
		/// <param name="dbnullvalue">The default value.</param>
		/// <returns></returns>
		protected Guid getGuid(string columnname, Guid dbnullvalue)
		{
			return (Guid)getValue(columnname, dbnullvalue);
		}

		/// <summary>
		/// Sets the value of a column.
		/// </summary>
		/// <param name="propName">Name of the property.</param>
		/// <param name="columnname">The column name.</param>
		/// <param name="value">The value.</param>
		protected void setValue(string propName, string columnname, object value)
		{
			if ( row == null || ( row.IsNull(this.colnmId) && this.id > 0 ) )
				this.Reload();
#if DEBUG
			if ( row == null ) {
				if ( columnname == colnmNm ) {
					this.name = ( value == null ? null : value.ToString() );
					return;
				} else
					throw new NullReferenceException("row is null.");
			}
			if ( !row.Table.Columns.Contains(columnname) )
				throw new Exception(string.Format("Column '{0}' doesn't exists.", columnname));
#else
			if ( row == null || !row.Table.Columns.Contains(columnname) )
				return;
#endif
			if ( value is int && (int)value <= 0 && columnname.EndsWith("_id") )
				value = null;

			object oldValue = this.row[columnname];
			if ( !oldValue.Equals(value) ) {
				MemberInfo member = this.GetType().GetProperty(propName);
				if ( OnItemChanging(member, value) ) {
					this.row[columnname] = ( value == null ? (object)DBNull.Value : value );
					OnItemChanged(member, oldValue, value);
				}
			}
		}
		/// <summary>
		/// Sets the value of a column.
		/// </summary>
		/// <param name="propName">Name of the property.</param>
		/// <param name="columnname">The column name.</param>
		/// <param name="value">The value.</param>
		/// <param name="nullValue">The null value. If the value equals the nullValue then DBNull.Value will be assigned to the specified column in the row.</param>
		protected void setValue(string propName, string columnname, object value, object nullValue)
		{
			if ( ( value != null && value == nullValue ) ||
				 ( value == null && nullValue == null ) ) {
				value = DBNull.Value;
			}
			setValue(propName, columnname, value);
		}
		/// <summary>
		/// Sets the string value of a column.
		/// </summary>
		/// <param name="propName">Name of the property.</param>
		/// <param name="columnname">The column name.</param>
		/// <param name="value">The value.</param>
		/// <param name="mode">The encryption/compression mode.</param>
		protected void setString(string propName, string columnname, string value, EncryptCompressMode mode)
		{
			if ( value != null ) {
				switch ( mode ) {
					case EncryptCompressMode.CompressOnly:
						value = ZIPPREFIX + Helpers.CompressString(value);
						break;
					case EncryptCompressMode.EncryptAndCompress:
						value = ENCZIPPREFIX + Helpers.EncryptCompressString(value);
						break;
				}
			}
			setValue(propName, columnname, value);
		}
		/// <summary>
		/// Sets the schema as the value of a column with compression.
		/// </summary>
		/// <param name="propName">Name of the property.</param>
		/// <param name="columnname">The column name.</param>
		/// <param name="schema">The schema.</param>
		protected void setSchema(string propName, string columnname, XmlSchema schema)
		{
			setSchema(propName, columnname, schema, EncryptCompressMode.CompressOnly);
		}
		/// <summary>
		/// Sets the schema as the value of a column.
		/// </summary>
		/// <param name="propName">Name of the property.</param>
		/// <param name="columnname">The column name.</param>
		/// <param name="schema">The schema.</param>
		/// <param name="encryptcompressmode">The encryption/compression mode.</param>
		protected void setSchema(string propName, string columnname, XmlSchema schema,
			EncryptCompressMode encryptcompressmode)
		{
			if ( schema != null ) {
				string str = null;
				XmlWriterSettings settings = new XmlWriterSettings();
				settings.Indent = false;
				settings.OmitXmlDeclaration = true;
				settings.ConformanceLevel = ConformanceLevel.Auto;
				using ( StringWriter sw = new StringWriter() )
				using ( XmlWriter xw = XmlWriter.Create(sw, settings) ) {
					try {
						schema.Write(xw);
						xw.Flush();
						str = sw.ToString();
					} catch ( StackOverflowException ) {
						MessageBox.Show("A Stack Overflow occured! The operation has been cancelled",
							"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
					}
				}
				setString(propName, columnname, str, encryptcompressmode);
			} else {
				setValue(propName, columnname, null);
			}
		}
		/// <summary>
		/// Sets the XML document as the value of a column with compression.
		/// </summary>
		/// <param name="propName">Name of the property.</param>
		/// <param name="columnname">The column name.</param>
		/// <param name="xd">The xml document.</param>
		protected void setXmlDocument(string propName, string columnname, XmlDocument xd)
		{
			setXmlDocument(propName, columnname, xd, EncryptCompressMode.CompressOnly);
		}
		/// <summary>
		/// Sets the XML document as the value of a column.
		/// </summary>
		/// <param name="propName">Name of the property.</param>
		/// <param name="columnname">The column name.</param>
		/// <param name="xd">The xml document.</param>
		/// <param name="encryptcompressmode">The encryption/compression mode.</param>
		protected void setXmlDocument(string propName, string columnname, XmlDocument xd,
			EncryptCompressMode encryptcompressmode)
		{
			if ( xd != null && xd.DocumentElement != null ) {
				string str = null;
				XmlWriterSettings settings = new XmlWriterSettings();
				settings.Indent = false;
				settings.OmitXmlDeclaration = true;
				settings.ConformanceLevel = ConformanceLevel.Auto;
				using ( StringWriter sw = new StringWriter() )
				using ( XmlWriter xw = XmlWriter.Create(sw, settings) ) {
					xd.DocumentElement.WriteTo(xw);
					xw.Flush();
					str = sw.ToString();
				}
				setString(propName, columnname, str, encryptcompressmode);
			} else {
				setValue(propName, columnname, null);
			}
		}
		/// <summary>
		/// Sets the XML serialized object as the value of a column with encryption and compression.
		/// </summary>
		/// <param name="propName">Name of the property.</param>
		/// <param name="columnname">The column name.</param>
		/// <param name="obj">The object to serialize.</param>
		protected void setXmlSerializedObject(string propName, string columnname, object obj)
		{
			setXmlSerializedObject(propName, columnname, obj, EncryptCompressMode.EncryptAndCompress);
		}
		/// <summary>
		/// Sets the XML serialized object as the value of a column.
		/// </summary>
		/// <param name="propName">Name of the property.</param>
		/// <param name="columnname">The column name.</param>
		/// <param name="obj">The object to serialize.</param>
		/// <param name="encryptcompressmode">The encryption/compression mode.</param>
		protected void setXmlSerializedObject(string propName, string columnname, object obj,
			EncryptCompressMode encryptcompressmode)
		{
			if ( obj == null ) {
				setValue(propName, columnname, obj);
			} else {
				string xmlstr = null;
				XmlSerializer xs = new XmlSerializer(obj.GetType());
				XmlWriterSettings settings = new XmlWriterSettings();
				settings.Indent = false;
				settings.OmitXmlDeclaration = true;
				using ( StringWriter sw = new StringWriter() )
				using ( XmlWriter xw = XmlWriter.Create(sw, settings) ) {
					xs.Serialize(xw, obj);
					xmlstr = sw.ToString();
				}
				setString(propName, columnname, xmlstr, encryptcompressmode);
			}
		}
		/// <summary>
		/// Sets the serialized object as the value of a column.
		/// </summary>
		/// <param name="propName">Name of the property.</param>
		/// <param name="columnname">The column name.</param>
		/// <param name="obj">The object to serialize.</param>
		protected void setSerializedObject(string propName, string columnname, object obj)
		{
			if ( obj == null ) {
				setValue(propName, columnname, obj);
			} else {
				IFormatter f = new BinaryFormatter();
				using ( MemoryStream ms = new MemoryStream() ) {
					f.Serialize(ms, obj);
					ms.Position = 0;
					using ( StreamReader sr = new StreamReader(ms) ) {
						setValue(propName, columnname, sr.ReadToEnd());
					}
				}
			}
		}
		#endregion

		protected enum EncryptCompressMode
		{
			None = 0,
			CompressOnly,
			EncryptAndCompress
		}
	}
}
